package com.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.MailUtil;
import com.example.lotterysystem.common.utils.SMSUtil;
import com.example.lotterysystem.controller.param.DrawPrizeParam;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.WinningRecordDO;
import com.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.example.lotterysystem.dao.mapper.WinningRecordMapper;
import com.example.lotterysystem.service.IDrawPrizeService;
import com.example.lotterysystem.service.activityStatus.IActivityStatusManager;
import com.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@Slf4j
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {
    @Autowired
    private IDrawPrizeService drawPrizeService;
    @Autowired
    private IActivityStatusManager activityStatusManager;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        //接收到队列中的消息
        log.info("接收到队列中的消息 message:{}", JacksonUtil.writeValueAsString(message));

        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);

        //处理抽奖流程
        try {

            //校验抽奖请求是否有效
            if(!drawPrizeService.checkDrawPrizeParam(param)){
                return;
            }
            //活动/奖品/人员状态扭转处理
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);

            //通知中奖者(邮箱, 短信)
            //采用异步处理
            syncExecute(winningRecordDOList);
        } catch (ServiceException e) {
            log.error("处理MQ 消息异常 {}:{}", e.getCode(), e.getMsg(), e);
            //如果发生异常, 需要保证事务一致性, 进行回滚
            rollback(param);
            //再次抛出异常
            throw e;

        } catch (Exception e) {
            log.error("处理MQ 消息异常", e);
            rollback(param);
            //再次抛出异常
            throw e;
        }


    }

    /**
     * 处理抽奖异常的回滚行为: 回复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        if(!statusNeedRollback(param)){
            //状态转换不需要回滚
            return;
        }
        rollbackStatus(param);
        if(!winnerNeedRollback(param)){
            //中奖者名单不需要回滚
            return;
        }
        rollbackWinner(param);
    }

    private void rollbackWinner(DrawPrizeParam param) {
        //删除中奖者
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否有中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    private void rollbackStatus(DrawPrizeParam param) {
        //逆向状态转换, 使用activityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动,奖品,人员状态是否扭转, 但是回滚时, 已经保证了事务一致性, 只需要判断人员/奖品是否扭转
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getPrizeId(), param.getActivityId());
        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())){
            //已经扭转, 需要回滚
            return true;
        }
        return false;
    }

    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //通过线程池threadPoolTaskExecutor

        //短信通知
        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        //邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));
    }

    /**
     * 发送邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            log.info("中奖列表为空, 不需要发邮件");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList){
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(), "中奖通知", context);

        }
    }

    /**
     * 发短信
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            log.info("中奖列表为空, 不需要发短信");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList){
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
           smsUtil.sendMessage("SMS_476750013",
                   winningRecordDO.getWinnerPhoneNumber().getValue(),
                   JacksonUtil.writeValueAsString(map));
        }
    }

    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

//    private void statusConvert(DrawPrizeParam param) {
//        //活动状态依赖奖品状态和人员状态, 扩展性, 灵活性, 维护性差
//        //解决: 设计模式(责任链设计模式, 策略模式)
//        //活动状态 RUNNING -> COMPLETED
//        //奖品状态 INIT -> COMPLETED
//        //人员状态 INIT -> COMPLETED
//
//        //1. 扭转奖品状态
//        //查询活动关联奖品信息, 不是COMPLETED, 扭转
//
//        //2. 扭转人员状态
//        //查询活动关联奖品信息, 不是COMPLETED, 扭转
//
//        //3. 扭转活动状态
//        //查询活动关联奖品信息, 不是COMPLETED, 并且全部奖品都抽完, 扭转
//
//        //4. 更新缓存
//
//    }


}
